#include <common.h>
#include <bootretry.h>
#include <cli.h>
#include <command.h>
#include <console.h>
#include <dm.h>
#include <edid.h>
#include <environment.h>
#include <errno.h>
#include <malloc.h>
#include <asm/byteorder.h>
#include <linux/compiler.h>
#include <asm/io.h>

struct dma_descriptor_t{
	u32		mem_lo;		// the least 32bits of DMA mem in EP, byte[0-3] 源地址(memory)
	u32		mem_hi;		// the significant 32bits of DMA source, byte[4-7] 源地址
	u32		axi;		// the property of axi request, byte[8-11] AXI Ax 通道属性
	u32		pci_lo;		// the least 32bits of DMA mem in RC, byte[12-15] 目标地址(pci)
	u32		pci_hi;		// the significant 32bits of DMA mem in RC, byte[16-19] 目标地址
	u32		pcie_header;	// PCIe TLP Header Attributes, byte[20-23] PCIE 报文头属性
	u32		pcie_header1;		// byte[24-27] 								PCIE 报文头属性
	u32		ctrl;		// Length of transfer in bytes byte[28-30], and Control Byte byte31 传输长度（ 0 表示最大长度，即 2^24B）
	u32		state;		// AXI 总线状态 PCIE 总线状态 DMA 通道状态AXI Bus Status at byte32, PCIe Bus Status at byte33 and Channel Status at byte 34, byte35 is reserved
	u32		next_lo;	// the least 32bits of next descriptor, byte[36-39]
	u32		next_hi;	// the significant 32bits of next descriptor, byte[40-43]

};

struct dma_para {
    unsigned int    mode;     // 工作模式：读(DMA_READ)或写(DMA_WRITE)
    unsigned long   src_addr; // 
    unsigned long   dst_addr; //    
    unsigned int    len;      // 读写长度
};

#define DMA_INTR_ENABLE         (0x1 << 24)
#define DMA_LIST_ENABLE         (0x1 << 29)

// mode value define
#define DMA_READ                0x0
#define DMA_WRITE               0x1
#define DMA_GO                  0x1

#define DMA_DESC_T_ADDR 0x80008000   //描述符地址
#define PCIE_CTRL_BASE_C0 0x29000000

#define DMA_CH_CTRL(chan) (0xc000 + chan*0x14 + 0)
#define DMA_CH_SP_LO(chan) (0xc000 + chan*0x14 + 4)
#define DMA_CH_SP_HI(chan) (0xc000 + chan*0x14 + 8)


static void init_dma_desc(struct dma_descriptor_t *desc, struct dma_para para)
{
	unsigned char i = 0;
	
		printf("\tdesc@0x%lx\n", (unsigned long)desc);
		
		desc[i].mem_lo = para.dst_addr & 0xffffffff;
		desc[i].mem_hi = para.dst_addr >> 32;
		
		desc[i].axi = 0x0;
		
		desc[i].pci_lo = para.src_addr & 0xffffffff;
		desc[i].pci_hi = para.src_addr >> 32;
		
		desc[i].pcie_header = 0x1;
		desc[i].pcie_header1 = 0x0;
		
		desc[i].ctrl = para.len;
		//desc[i].ctrl = DMA_INTR_ENABLE | desc->ctrl;
		
		desc[i].state = 0x0;
		
		desc[i].next_lo = 0;
		desc[i].next_hi = 0;
			
	
}

static int init_dma(unsigned long src,unsigned long dst, unsigned int len)
{

	void *descript = NULL;
	struct dma_para	req;

	descript = (void *)DMA_DESC_T_ADDR;

	req.mode = 0;
	req.src_addr = src;
    req.dst_addr = dst;  
	req.len = len;
    
	init_dma_desc(descript, req);
    
    return 0;
}

static void start_dma(unsigned char dma_mode, unsigned long  desc_t_addr)
{
    unsigned int value;

    value = desc_t_addr & 0xffffffff;
    writel(value, PCIE_CTRL_BASE_C0 + DMA_CH_SP_LO(0));
	
    value = (desc_t_addr >> 32);
    writel(value, PCIE_CTRL_BASE_C0 + DMA_CH_SP_HI(0));
	
    writel((dma_mode<<1) | DMA_GO, PCIE_CTRL_BASE_C0 + DMA_CH_CTRL(0));
    
}

static int do_pci_dma_write(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
	ulong src_addr, dst_addr;
	uint len;

	if (argc != 4)
		return CMD_RET_USAGE;

	src_addr = simple_strtoul(argv[1], NULL, 16);
	dst_addr = simple_strtoul(argv[2], NULL, 16);
	len      = simple_strtoul(argv[3], NULL, 16);
	
	init_dma(src_addr, dst_addr, len);
	
	start_dma(DMA_WRITE, DMA_DESC_T_ADDR);

	return 0;
}

static int do_pci_dma_read(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
	ulong src_addr, dst_addr;
	uint len;

	if (argc != 4)
		return CMD_RET_USAGE;

	src_addr = simple_strtoul(argv[1], NULL, 16);
	dst_addr = simple_strtoul(argv[2], NULL, 16);
	len      = simple_strtoul(argv[3], NULL, 16);
	
	init_dma(src_addr, dst_addr, len);
	
	start_dma(DMA_READ, DMA_DESC_T_ADDR);

	return 0;
}

U_BOOT_CMD(pdmaw, 4, 0, do_pci_dma_write,
	   "pci_dma Write Test", 
	   "pci_addr mem_addr len" 
	   );



U_BOOT_CMD(pdmar, 4, 0, do_pci_dma_read,
	   "pci_dma Read Test", 
	   "pci_addr mem_addr len" 
	   );



